#include "ExpressionsManager.hpp"
#include "Expression.hpp"
#include "Type.hpp"
#include "Value.hpp"
#include "Instruction.hpp"
#include "3adCode.hpp"
#include "SymbolTemp.hpp"
#include "ErrorCodes.hpp"
#include "TypeBase.hpp"

#include <iostream>
using namespace std;

ExpressionsManager::ExpressionsManager(IdentTable* identTable, SymbolTablesManager* symbolTablesManager) {
	this->identTable = identTable;
	this->symbolTablesManager = symbolTablesManager;
}



ExpressionsManager::~ExpressionsManager() {}


Expression* ExpressionsManager::newExpressionFromToken(Type* tokenType, Value* tokenValue) {
	Expression* expr = new Expression();
	expr->setType(tokenType);
	expr->setValue(tokenValue);
	expr->setCalculated(true);
	expr->setAdressable(false);
	return expr;
}

Expression* ExpressionsManager::newCalculatedExpression(Type* tokenType, Value* value) {
	Expression* expr = new Expression();
	expr->setType(tokenType);
	expr->setValue(value);
	expr->setCalculated(true);
	expr->setAdressable(false);

	/*code = new CodeContainer();
	code->add(new Instruction(OPCODE_NONE));
	????
	*/
	return expr;
}


Expression* ExpressionsManager::newExpressionFromNil() {
	Expression * expr = new Expression();
	expr->setType(new TypeNil());
	expr->setValue(new NilValue());
	expr->setCalculated(true);
	return expr;
}

Expression* ExpressionsManager::newNonCalculatedExpression(Type* tokenType, int identTempResult, CodeContainer* code) {
	Expression* expr = new Expression();
	expr->setType(tokenType);
	expr->setCalculated(false);
	expr->setAdressable(false);//TODO depend du type...
	expr->setIdentTempResult(identTempResult);
	expr->setCode(code);
	return expr;
}
Expression* ExpressionsManager::binaryOperation(Expression* e1, CodeInstruction binaryOperator, Expression* e2) {
	Type * type;
	
	//TODO compatibilité entre type plutôt que égalité
	if (e1->getType() == e2->getType()) {
		type = e1->getType()->clone(); //TODO type le + "élevé" (ex: real > integer)
	} else {
		type = e1->getType()->clone(); //TODO enlever ça et mettre le truc du dessous
//		throw INCOMPATIBLE_TYPES_BETWEEN_EXPRESSIONS;
	}
	if (e1->isCalculated() && e2->isCalculated()) {
		Value* value = Value::calculate(binaryOperator, e1->getValue(), e2->getValue());
		return newCalculatedExpression(type, value);
		
		
	} else if (e1->isCalculated() &&  ! e2->isCalculated() ) {
		int identTempResult = identTable->putTemp();
		symbolTablesManager->getCurrent()->add(identTempResult, new SymbolTemp(type));
		
		CodeContainer * code = e2->getCode();
		code->add(new Instruction(binaryOperator, new Operand(identTempResult), new Operand(e1->getValue()), new Operand(e2->getIdentTempResult())));
		
		return newNonCalculatedExpression(type, identTempResult,  code);
	
	} else if ( ! e1->isCalculated() &&  e2->isCalculated() ) {
		int identTempResult = identTable->putTemp();
		symbolTablesManager->getCurrent()->add(identTempResult, new SymbolTemp(type));
		
		CodeContainer * code = e1->getCode();
		code->add(new Instruction(binaryOperator, new Operand(identTempResult), new Operand(e1->getIdentTempResult()), new Operand(e2->getValue())));
		
		return newNonCalculatedExpression(type, identTempResult,  e1->getCode());
		
	} else if (e1->isCalculated() && ! e2->isCalculated()) {
		int identTempResult = identTable->putTemp();
		symbolTablesManager->getCurrent()->add(identTempResult, new SymbolTemp(type));
		
		CodeContainer * code = e1->getCode();
		code->add(e2->getCode());
		code->add(new Instruction(binaryOperator, new Operand(identTempResult), new Operand(e1->getIdentTempResult()), new Operand(e2->getIdentTempResult())));
		
		return newNonCalculatedExpression(type, identTempResult,  e1->getCode());
	} else {
		throw INTERNAL_ERRROR;
	}
}

Expression* ExpressionsManager::unaryOperation(CodeInstruction unaryOperator, Expression* e) {
	Type * type;

	if (e->getType()->isApplicable(unaryOperator)) {
		type = e->getType();
	} else {
		throw TYPE_INCOMPATIBLE_WITH_OPERATOR;
	}

	if (e->isCalculated()) {
		Value* value = Value::calculate(unaryOperator, e->getValue());
		return newCalculatedExpression(type, value);

	} else if ( ! e->isCalculated()) {
		int identTempResult = identTable->putTemp();
		symbolTablesManager->getCurrent()->add(identTempResult, new SymbolTemp(type));

		CodeContainer * code = e->getCode();
		code->add(new Instruction(unaryOperator, new Operand(identTempResult), new Operand(e->getIdentTempResult())));

		return newNonCalculatedExpression(type, identTempResult,  e->getCode());
	} else {
		throw INTERNAL_ERRROR;
	}
}


